﻿/* System.Compute: a Computation Framework for .NET/Mono
 * Copyright (C) 2011 Ralf "raboni" Boeckhorst
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

using System.Runtime.InteropServices;

namespace System.Compute.OpenCL
{
    public sealed class Context : ICloneable, IDisposable
    {
        delegate void pfnNotify(string errinfo, byte[] private_info, SizeT cb, byte[] user_data);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern IntPtr clCreateContext(IntPtr[] properties, uint num_devices, IntPtr[] devices, pfnNotify notify, byte[] user_data, ref ErrorCode errcode_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern IntPtr clCreateContextFromType(IntPtr[] properties, DeviceType device_type, pfnNotify notify, byte[] user_data, ref ErrorCode errcode_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetContextInfo(IntPtr context, ContextInfo param_name, SizeT param_value_size, IntPtr param_value, ref SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetContextInfo(IntPtr context, ContextInfo param_name, SizeT param_value_size, ref uint param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetContextInfo(IntPtr context, ContextInfo param_name, SizeT param_value_size, IntPtr[] param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clRetainContext(IntPtr context);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clReleaseContext(IntPtr context);

        public uint ReferenceCount
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetContextInfo(Handle, ContextInfo.ReferenceCount, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NumDevices
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetContextInfo(Handle, ContextInfo.NumDevices, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public Device[] Devices
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetContextInfo(Handle, ContextInfo.Devices, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                IntPtr[] tmp = new IntPtr[(long)param_value_size / (long)param_value_size.Size];
                res = clGetContextInfo(Handle, ContextInfo.Devices, param_value_size, tmp, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                Device[] ret = new Device[tmp.LongLength];
                for (long i = 0; i < tmp.LongLength; i++)
                {
                    ret[i] = new Device(tmp[i]);
                }
                return ret;
            }
        }

        public IntPtr[] Properties
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetContextInfo(Handle, ContextInfo.Properties, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                IntPtr[] ret = new IntPtr[(long)param_value_size / (long)param_value_size.Size];
                res = clGetContextInfo(Handle, ContextInfo.Properties, param_value_size, ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        internal IntPtr Handle;

        internal Context(IntPtr handle)
        {
            Handle = handle;
        }

        public Context(Platform platform, params Device[] devices)
        {
            IntPtr[] properties = new IntPtr[3];
            properties[0] = new IntPtr((int)ContextProperties.Platform);
            properties[1] = platform.Handle;
            properties[2] = IntPtr.Zero;
            ErrorCode res = ErrorCode.Success;

            IntPtr[] p_devices = new IntPtr[devices.LongLength];
            for (long i = 0; i < devices.LongLength; i++)
                p_devices[i] = devices[i].Handle;

            Handle = clCreateContext(properties, (uint)devices.Length, p_devices, null, null, ref res);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public Context(Platform platform, DeviceType device_type)
        {
            IntPtr[] properties = new IntPtr[3];
            properties[0] = new IntPtr((int)ContextProperties.Platform);
            properties[1] = platform.Handle;
            properties[2] = IntPtr.Zero;
            ErrorCode res = ErrorCode.Success;

            Handle = clCreateContextFromType(properties, device_type, null, null, ref res);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public object Clone()
        {
            ErrorCode res = clRetainContext(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            return new Context(Handle);
        }

        public void Dispose()
        {
            ErrorCode res = clReleaseContext(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            Handle = IntPtr.Zero;
        }
    }
}
